﻿using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

namespace Jinndev.UI {

    /// <summary>
    /// 日志视图
    /// </summary>
    public class LogView : MonoBehaviour {

        #region static
        public static readonly string PATH = "Jinndev/Prefab/Log/LogView";

        public static LogView Create(Transform container) {
            return CommonUtil.Instantiate<LogView>(PATH, container);
        }
        #endregion

        private struct Data {
            public string message;
            public Color iconColor;
            public UnityAction onClick;
        }

        public GameObject itemPrefab = null;
        public ScrollRect scrollRect = null;

        public Color[] itemBgColors = new Color[] { new Color(0.1f, 0.1f, 0.1f), new Color(0.2f, 0.2f, 0.2f) };
        public Color[] itemTextColors = new Color[] { new Color(1, 1, 1), new Color(1, 1, 1) };

        public Color infoColor = new Color(0.2f, 0.6f, 0.7f);
        public Color warnColor = new Color(0.8f, 0.7f, 0.2f);
        public Color errorColor = new Color(0.8f, 0.2f, 0.2f);

        public bool enableDelete = false;

        [Header("Layout")]
        public RectOffset padding = new RectOffset();
        public float spacing = 0;

        private LogItem templateItem;

        private List<Data> dataList = new List<Data>();
        private List<float> heightList = new List<float>();
        private Stack<GameObject> pool = new Stack<GameObject>();
        private Dictionary<int, GameObject> visibleItems = new Dictionary<int, GameObject>();

        private void Awake() {
            scrollRect.onValueChanged.AddListener(OnScroll);

            scrollRect.content.anchorMin = new Vector2(0, 1);
            scrollRect.content.anchorMax = new Vector2(1, 1);
            scrollRect.content.pivot = new Vector2(0, 1);
            scrollRect.content.offsetMin = new Vector2(0, 0);
            scrollRect.content.offsetMax = new Vector2(0, 0);

        }

        private void Start() {
            CreateTemplateItem();
            //StartCoroutine(Test());
        }

        //private IEnumerator Test() {
        //    for (int i = 0; i < 10; i++) {
        //        string line = "line" + i;
        //        for (int j = 0; j < Random.Range(5, 8); j++) {
        //            line += "\nline";
        //        }
        //        if (i % 3 == 0) {
        //            Log(line);
        //        }
        //        else if (i % 3 == 1) {
        //            LogWarn(line);
        //        }
        //        else {
        //            LogError(line);
        //        }

        //        yield return new WaitForSeconds(0.5f);
        //    }
        //}

        private void CreateTemplateItem() {
            UIUtil.ForceLayout(transform);

            GameObject obj = Instantiate(itemPrefab, transform, false);
            InitItem(obj);
            templateItem = obj.GetComponent<LogItem>();

            RectTransform objTransform = obj.transform as RectTransform;
            objTransform.anchoredPosition = new Vector2(Screen.width * 2, Screen.height * 2);
            objTransform.localScale = Vector3.zero;
        }

        public void Log(object message, UnityAction onClick) {
            AppendItem(infoColor, message == null ? "Null" : message.ToString(), onClick);
        }

        public void Log(string message) {
            AppendItem(infoColor, message, null);
        }

        public void LogFormat(string format, params object[] args) {
            AppendItem(infoColor, string.Format(format, args), null);
        }

        public void Log(UnityAction onClick, string message) {
            AppendItem(infoColor, message, onClick);
        }

        public void LogFormat(UnityAction onClick, string format, params object[] args) {
            AppendItem(infoColor, string.Format(format, args), onClick);
        }

        public void LogWarn(object message, UnityAction onClick) {
            AppendItem(warnColor, message == null ? "Null" : message.ToString(), onClick);
        }

        public void LogWarn(string message) {
            AppendItem(warnColor, message, null);
        }

        public void LogWarnFormat(string format, params object[] args) {
            AppendItem(warnColor, string.Format(format, args), null);
        }

        public void LogWarn(UnityAction onClick, string message) {
            AppendItem(warnColor, message, onClick);
        }

        public void LogWarnFormat(UnityAction onClick, string format, params object[] args) {
            AppendItem(warnColor, string.Format(format, args), onClick);
        }

        public void LogError(object message, UnityAction onClick) {
            AppendItem(errorColor, message == null ? "Null" : message.ToString(), onClick);
        }

        public void LogError(string message) {
            AppendItem(errorColor, message, null);
        }

        public void LogErrorFormat(string format, params object[] args) {
            AppendItem(errorColor, string.Format(format, args), null);
        }

        public void LogError(UnityAction onClick, string message) {
            AppendItem(errorColor, message, onClick);
        }

        public void LogErrorFormat(UnityAction onClick, string format, params object[] args) {
            AppendItem(errorColor, string.Format(format, args), onClick);
        }

        public void LogException(System.Exception e, UnityAction onClick = null) {
            LogError(onClick, e == null ? "Null" : e.Message);
        }

        public void Clear() {
            dataList.Clear();
            heightList.Clear();
            Refresh(false);
        }

        private void AppendItem(Color iconColor, string message, UnityAction onClick) {
            if (templateItem == null) {
                CreateTemplateItem();
            }
            //// 当前索引
            //int index = dataList.Count;

            // 使用模板计算条目的高度
            templateItem.Refresh(message);
            float height = templateItem.Height;

            // 保存高度和日志数据
            heightList.Add(height);
            dataList.Add(new Data { message = message, iconColor = iconColor, onClick = onClick });

            OnChangeData();
        }

        public void RemoveLast() {
            if (dataList.Count == 0) {
                return;
            }
            int index = dataList.Count - 1;
            dataList.RemoveAt(index);
            heightList.RemoveAt(index);

            OnChangeData();
        }

        private void OnChangeData() {
            // 是否需要自动滚动
            bool autoRollToEnd = scrollRect.verticalNormalizedPosition <= 0.01f;

            // 更新content大小
            //float y = GetPositionY(index);
            //float totalHeight = Mathf.Abs(y) + height + padding.bottom;
            //UIUtil.SetSizeDeltaHeight(scrollRect.content, totalHeight);
            //ResetContentSize();

            if (autoRollToEnd) {
                scrollRect.DOKill();
                scrollRect.DOVerticalNormalizedPos(0, 0.15f);
            }

            // 不需要手动调用OnScroll，ScrollRect会自动调用
            //OnScroll(scrollRect.normalizedPosition);
            Refresh(false);
        }

        private void ResetContentSize() {
            float totalHeight = heightList.Sum() + padding.vertical + spacing * dataList.Count;
            UIUtil.SetSizeDeltaY(scrollRect.content, totalHeight);
        }

        private void RefreshItem(GameObject obj, int index) {
            Data data = dataList[index];

            Color bgColor, textColor;
            if (itemBgColors.Length == 0) {
                bgColor = new Color(0.1f, 0.1f, 0.1f);
                textColor = new Color(1, 1, 1);
            }
            else {
                int colorIndex = index % itemBgColors.Length;
                bgColor = itemBgColors[colorIndex];
                textColor = colorIndex < itemTextColors.Length ? itemTextColors[colorIndex] : new Color(1, 1, 1);
            }

            // 设置文本，并自适应高度
            LogItem item = obj.GetComponent<LogItem>();
            item.Refresh(data.message, bgColor, textColor, data.iconColor, enableDelete, data.onClick);
            item.onDelete = () => {
                heightList.RemoveAt(index);
                dataList.RemoveAt(index);
                Refresh(true);
            };
            // 设置坐标
            SetItemPosition(item, GetPositionY(index));
        }

        private void InitItem(GameObject obj) {
            // 设置宽度，为下一步自适应高度做准备
            RectTransform rectTransform = obj.GetComponent<RectTransform>();
            rectTransform.anchorMin = new Vector2(0.5f, 1);
            rectTransform.anchorMax = new Vector2(0.5f, 1);
            rectTransform.pivot = new Vector2(0.5f, 1);
            UIUtil.SetSizeDeltaX(rectTransform, scrollRect.viewport.rect.width - padding.horizontal);
        }

        private void SetItemPosition(LogItem item, float y) {
            RectTransform rectTransform = item.transform as RectTransform;
            rectTransform.anchoredPosition = new Vector2(0, y);
        }

        /// <summary>
        /// 得到指定索引的条目的y坐标
        /// </summary>
        private float GetPositionY(int index) {
            float y = padding.top;
            for (int i = 0; i < index; i++) {
                y += heightList[i];
                y += spacing;
            }
            return -y; // 正向是负值
        }

        private void OnScroll(Vector2 normalizedPosition) {
            Refresh(false);
        }

        private void Refresh(bool refreshAll) {
            ResetContentSize();

            // content的y坐标，初始为0，向下滚动时content上移，y增大
            float y = scrollRect.content.anchoredPosition.y;

            // 计算可见范围的索引上下限
            float topY = y;
            float bottomY = topY + scrollRect.viewport.rect.height;
            int topIndex = 0;
            int bottomIndex = dataList.Count - 1;

            float height = padding.top;
            for (int i = 0; i < dataList.Count; i++) {
                if (i > 0) {
                    height += spacing;
                }
                height += heightList[i];

                // 找到第一个看的见的索引
                bool visible = topY < height;
                if (visible) {
                    topIndex = i; // 得到可见索引上限
                    break;
                }
            }

            for (int i = topIndex + 1; i < dataList.Count; i++) {
                height += spacing;

                // 找到第一个看不见的索引
                bool invisible = bottomY < height;
                if (invisible) {
                    bottomIndex = i - 1; // 则可见索引下限为i-1
                    break;
                }
                height += heightList[i];
            }

            // 变为不可见的回收
            foreach (var index in visibleItems.Keys.ToArray()) {
                if (index < topIndex || index > bottomIndex) {
                    GameObject obj = visibleItems[index];
                    visibleItems.Remove(index);
                    CacheItem(obj);
                }
            }

            // 变为可见的创建
            for (int index = topIndex; index <= bottomIndex; index++) {
                if (!visibleItems.ContainsKey(index)) {
                    GameObject obj = PopItem();
                    visibleItems[index] = obj;

                    if (!refreshAll) {
                        RefreshItem(obj, index);
                    }
                }
            }

            if (refreshAll) {
                foreach (var pair in visibleItems) {
                    int index = pair.Key;
                    GameObject obj = pair.Value;
                    RefreshItem(obj, index);
                }
            }
        }


        private GameObject PopItem() {
            if (pool.Count > 0) {
                GameObject obj = pool.Pop();
                obj.SetActive(true);
                return obj;
            }
            else {
                GameObject obj = Instantiate(itemPrefab, scrollRect.content, false);
                InitItem(obj);
                return obj;
            }
        }

        private void CacheItem(GameObject obj) {
            obj.SetActive(false);
            pool.Push(obj);
        }



    }

}
